using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SynapseGaming.LightingSystem.Core;
using SynapseGaming.LightingSystem.Rendering;

namespace Indiefreaks.Xna.Rendering
{
#if SUNBURN_PRO && !WINDOWS_PHONE
    /// <summary>
    /// Provides a complete deferred renderer with support for custom renderered entities
    /// </summary>
    public class DeferredRenderer : SynapseGaming.LightingSystem.Rendering.Deferred.DeferredRenderManager
    {
        private readonly List<SceneEntity> _entitiesInView;
        private readonly List<SceneEntity> _visibleAndSorted;

        /// <summary>
        /// Creates a new instance
        /// </summary>
        /// <param name="graphicsdevicemanager"></param>
        /// <param name="sceneinterface"></param>
        public DeferredRenderer(IGraphicsDeviceService graphicsdevicemanager, IManagerServiceProvider sceneinterface) : base(graphicsdevicemanager, sceneinterface)
        {
            // our lists to maintain Renderers and those who will be rendered in a sort order
            _entitiesInView = new List<SceneEntity>();
            _visibleAndSorted = new List<SceneEntity>();   
        }

        /// <summary>
        /// Removes all scene objects and cleans up scene information.
        /// </summary>
        public override void Clear()
        {
            base.Clear();

            _visibleAndSorted.Clear();
        }

        /// <summary>
        /// Builds all object batches, shadow maps, and cached information before rendering.
        /// It also sorts rendering custom entities sorted based on their distance from camera.
        /// Any object added to the RenderManager after this call will not be visible during the frame.
        /// </summary>
        /// <param name="scenestate"/>
        public override void BeginFrameRendering(ISceneState scenestate)
        {
            // We clear the entities' lists so they can fetchup new ones
            _entitiesInView.Clear();
            _visibleAndSorted.Clear();

            // We retrieve the ObjectManager and look for all entities in view
            var manager = ServiceProvider.GetManager<IObjectManager>(true);
            manager.Find(_entitiesInView, scenestate.ViewFrustum, ObjectFilter.All);

            // For each entity that we manually render, we add them to our own collection for future use
            // Remark: because manually render entities are inheriting from SceneEntity, they don't get rendered by SunBurn
            foreach (var sceneEntity in _entitiesInView)
            {
                if (sceneEntity is IRenderEntity && ((IRenderEntity)sceneEntity).Show)
                {
                    _visibleAndSorted.Add(sceneEntity);
                }
            }
            
            if (SceneState != null)
            {
                // we sort all the Renderers based on their distance from the camera position
                _visibleAndSorted.Sort(SortBasedOnDistanceFromCamera);
            }

            base.BeginFrameRendering(scenestate);
        }

        /// <summary>
        /// Renders the scene.
        /// </summary>
        public override void Render()
        {
            base.Render();

            var graphicsDevice = GraphicsDeviceManager.GraphicsDevice;
            graphicsDevice.DepthStencilState = DepthStencilState.DepthRead;
            graphicsDevice.SamplerStates[0] = SamplerState.AnisotropicClamp;

            foreach (IRenderEntity renderer in _visibleAndSorted)
            {
                renderer.Render(SceneState, graphicsDevice);
            }

            graphicsDevice.DepthStencilState = DepthStencilState.Default;
        }

        /// <summary>
        ///   Sorts Renderers so that they are rendered based on their distance to the camera. Required for AlphaBlending
        /// </summary>
        /// <param name = "x"></param>
        /// <param name = "y"></param>
        /// <returns></returns>
        private int SortBasedOnDistanceFromCamera(ISceneEntity x, ISceneEntity y)
        {
            // We first check if one of our entities should be rendered from anywhere so that it gets placed at the beginning of our sorted entities
            if (x.InfiniteBounds && !y.InfiniteBounds)
                return -1;
            if (!x.InfiniteBounds && y.InfiniteBounds)
                return 1;

            var xDistanceFromCamera = Vector3.DistanceSquared(x.World.Translation,
                                                                SceneState.ViewToWorld.Translation);
            var yDistanceFromCamera = Vector3.DistanceSquared(y.World.Translation,
                                                                SceneState.ViewToWorld.Translation);

            if (xDistanceFromCamera == yDistanceFromCamera)
                return 0;
            return xDistanceFromCamera > yDistanceFromCamera ? -1 : 1;
        }
    }
#endif
}